package UI.Table;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.sql.Date;
import java.text.ParseException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import org.jdesktop.swingx.JXPanel;

import Task.DSTask;
import UI.DSUILayout;

//@author A0103535R
/************************************ Abstract Class DSTaskTableCell ***************************/
public class DSTaskTableCell extends JXPanel {
	private static final long serialVersionUID = 1L;
	public static final int DSTASKTABLECELL_SHADOW_EXTEND_Y = 7;
	public static final int DSTASKTABLECELL_SHADOW_EXTEND_X = 10;
	public static final int DSTASKTABLECELL_SHADOW_SIZE = DSTASKTABLECELL_SHADOW_EXTEND_Y - 2;
	public static final int DSTASKTABLECELL_ROUNDED_CORNER_RADIUS = 8;

	// Color For Cell Symbol
	private static final Color DONE_COLOR = new Color(103, 108, 118, 225);
	private static final Color NOT_DONE_COLOR = new Color(82, 89, 106, 170);
	private static final Color INPROCESS_COLOR = new Color(253, 169, 71, 185);
	private static final Color OVERDUE_COLOR = new Color(166, 77, 121, 255);
	private static final Color NOT_DONE_BACKGROUND_COLOR = new Color(82, 89,
			106, 130);
	private static final Color DONE_BACKGROUND_COLOR = new Color(65, 71, 83,
			230);

	protected static BufferedImage _doneImage;
	private DSTask _task;
	private BufferedImage _graphicImage;
	private Color _backgroundColor = NOT_DONE_BACKGROUND_COLOR;
	private DSTaskTableCellTransparentLabel _taskNameLabel;
	private DSTaskTableCellColoredLabel _taskIndexLabel;
	private DSTaskTableCellColoredLabel _taskStartDateLabel;
	private DSTaskTableCellColoredLabel _taskEndDateLabel;
	private ArrayList<DSTaskTableCellColoredLabel> _taskTagLabels;

	private int _width = DSUILayout.TABLE_CELL_WIDTH_MAXIMUM;
	private int _height = DSUILayout.TABLE_CELL_Height_MININUM;
	private int _taskIndexInteger;
	private int _status = DSTask.NOT_DONE;
	private boolean _isSimplified = false;

	/**
	 * This is the constructor of DSTaskTableCell
	 */
	public DSTaskTableCell(DSTask task, int index, boolean isSimplified)
			throws IOException, ParseException {
		super();
		_taskIndexInteger = index;
		_task = task;
		_isSimplified = isSimplified;
		checkFlags();
		loadImages();
		initAppearance();
		initLabels();
		layoutLabels();
		processGraphics();
	}

	/**
	 * This function is used to get the height of cell
	 * 
	 * @return the height
	 */
	public int getHeight() {
		return _height;
	}

	/**
	 * This function is used to get the width of cell
	 * 
	 * @return the width
	 */
	public int getWidth() {
		return _width;
	}

	/**
	 * This function is used to load the image for symbol
	 */
	private void loadImages() {
		try {
			if (_doneImage == null) {
				URL imageUrl = this.getClass().getClassLoader()
						.getResource("complete.png");
				_doneImage = ImageIO.read(imageUrl);
			}
		} catch (IOException ignore) {
		}
	}

	/**
	 * This function is used to check the status of task
	 */
	private void checkFlags() {
		_status = _task.getStatus();
	}

	/**
	 * This function is used to initialize its appearance
	 */
	private void initAppearance() {
		this.setLayout(null);
		setOpaque(false);
		if (_status == DSTask.DONE) {
			_backgroundColor = DONE_BACKGROUND_COLOR;
		}
	}

	/**
	 * This function is used to initialize its labels
	 */
	private void initLabels() {
		try {
			initTaskIndex(_taskIndexInteger);
			initTaskName(_task.getTaskName());
			if (isStartDatePassed()) {// Start Date Passed
				initTaskStartDate(_task.StartDateandTime(),
						DSTaskTableCellColoredLabel.DATE_TYPE_PASSED);
				initTaskEndDate(_task.EndDateandTime(),
						(int) _task.end_minus_today());
			} else if (hasStartDateORTime()) {// Both
				initTaskStartDate(_task.StartDateandTime(),
						(int) _task.start_minus_today());
				initTaskEndDate(_task.EndDateandTime(),
						DSTaskTableCellColoredLabel.DATE_TYPE_FAR);
			} else { // Only End Date
				initTaskEndDate(_task.EndDateandTime(),
						(int) _task.end_minus_today());
			}
			if (_task.hasTags())
				initTaskTags(_task.getTaskTagStrings());
			if (_status == DSTask.DONE)
				setLabelsDoneColor();
		} catch (ParseException ignore) {
		}

	}

	/**
	 * This function is used to check whether the task has start date or start
	 * time.
	 * 
	 * @return the result
	 */
	private boolean hasStartDateORTime() {
		return _task.hasStartDate() || _task.hasStartTime();
	}

	/**
	 * This function is used to check whether the start date of task has passed
	 * 
	 * @return result
	 */
	private boolean isStartDatePassed() throws ParseException {
		return (int) _task.end_minus_today() <= 0
				|| (int) _task.start_minus_today() <= 0;
	}

	/**
	 * This function is used to initialize the index label
	 * 
	 * @param index
	 *            the index of task for display
	 */
	private void initTaskIndex(int index) {
		_taskIndexLabel = new DSTaskTableCellColoredLabel(index);
		_taskIndexLabel.resize();
	}

	/**
	 * This function is used to initialize the name label
	 * 
	 * @param name
	 *            name of task
	 */
	private void initTaskName(String name) {
		_taskNameLabel = new DSTaskTableCellTransparentLabel(name);
		_taskNameLabel.setForeground(new Color(255, 255, 255));
		_taskNameLabel.setFont(new Font("Calibri", Font.PLAIN, 16));
		_taskNameLabel.resize();
	}

	/**
	 * This function is used to initialize the start date label
	 * 
	 * @param startDate
	 *            the start date of task
	 * @param timeLeft
	 *            how much time left of its start date
	 */
	private void initTaskStartDate(String startDate, int timeLeft) {
		_taskStartDateLabel = new DSTaskTableCellColoredLabel(startDate,
				timeLeft);
		_taskStartDateLabel.setForeground(new Color(255, 255, 255));
		_taskStartDateLabel.resize();
	}

	/**
	 * This function is used to initialize the end date label
	 * 
	 * @param endDate
	 *            the end date of task
	 * @param timeLeft
	 *            how much time left of its end date
	 */
	private void initTaskEndDate(String endDate, int timeLeft) {
		_taskEndDateLabel = new DSTaskTableCellColoredLabel(endDate, timeLeft);
		_taskEndDateLabel.setForeground(new Color(255, 255, 255));
		_taskEndDateLabel.resize();
	}

	/**
	 * This function is used to initialize tag labels
	 * 
	 * @param tags
	 *            tags of task
	 */
	private void initTaskTags(String[] tags) {
		_taskTagLabels = new ArrayList<DSTaskTableCellColoredLabel>();
		DSTaskTableCellColoredLabel label = null;
		for (int i = 0; i < tags.length; i++) {
			label = new DSTaskTableCellColoredLabel(tags[i]);
			label.resize();
			_taskTagLabels.add(label);
		}
	}

	/**
	 * This function is used to set the color of labels as done color
	 */
	private void setLabelsDoneColor() {
		_taskNameLabel.setForeground(new Color(179, 181, 189));
		_taskStartDateLabel.setDateDoneColor();
		_taskEndDateLabel.setDateDoneColor();
		_taskIndexLabel.setIndexDoneColor();
	}

	/**
	 * This function is used to layout the labels
	 */
	private void layoutLabels() {
		// Initialize important values
		int most_right_x = (_taskStartDateLabel.getWidth() > _taskEndDateLabel
				.getWidth()) ? (DSUILayout.TABLE_CELL_WIDTH_MAXIMUM
				- _taskStartDateLabel.getWidth() - 30)
				: (DSUILayout.TABLE_CELL_WIDTH_MAXIMUM
						- _taskEndDateLabel.getWidth() - 30);
		boolean is_height_extend = (_status == DSTask.DONE || _isSimplified || (!_task
				.hasTags() && !_isSimplified)); // maximum x value allowed
		int x = DSUILayout.TABLE_CELL_SIGN_WIDTH + 10 // x value of coordinate
				+ DSTASKTABLECELL_SHADOW_EXTEND_X;
		int y = 8 + DSTASKTABLECELL_SHADOW_EXTEND_Y; // y value of coordinate

		// Increase cell height if there are two dates
		if (!_task.StartDateandTime().equals("")
				&& !_task.EndDateandTime().equals("")) {
			if (is_height_extend) {
				y += 7;
			}
		}

		// Set bounds of index label
		_taskIndexLabel.setBounds(x, y, _taskIndexLabel.getWidth(),
				_taskIndexLabel.getHeight());
		this.add(_taskIndexLabel);
		x += 5 + _taskIndexLabel.getWidth();

		// Resize name labels if necessary
		if (most_right_x - x < DSTaskTableCellTransparentLabel.MAXIMUM_WIDTH) {
			_taskNameLabel.resize(most_right_x - x);
		}

		// Set bounds of name label
		_taskNameLabel.setBounds(x, y - 3, _taskNameLabel.getWidth(),
				_taskNameLabel.getHeight());
		y += _taskNameLabel.getHeight() - 4;
		this.add(_taskNameLabel);

		// Set bound of tag labels if necessary
		if (_task.hasTags() && !_isSimplified && _status != DSTask.DONE) {
			x = DSUILayout.TABLE_CELL_SIGN_WIDTH + 10
					+ DSTASKTABLECELL_SHADOW_EXTEND_X;
			y += 5;

			// Set bound of each tag label
			for (int i = 0; i < _taskTagLabels.size(); i++) {
				DSTaskTableCellColoredLabel tagLabel = _taskTagLabels.get(i);
				if (x + tagLabel.getWidth() <= most_right_x) {
					tagLabel.setBounds(x, y, tagLabel.getWidth(),
							tagLabel.getHeight());
					x += tagLabel.getWidth() + 5;
				} else {
					y += tagLabel.getHeight() + 5;
					x = DSUILayout.TABLE_CELL_SIGN_WIDTH + 10
							+ DSTASKTABLECELL_SHADOW_EXTEND_X;
					i--;
					continue;
				}
				add(tagLabel);
			}
			y += _taskTagLabels.get(0).getHeight();
		}
		y += 8 + DSTASKTABLECELL_SHADOW_EXTEND_Y;
		_height = y;

		// Set bound of date label(s)
		if (!_task.StartDateandTime().equals("")
				&& !_task.EndDateandTime().equals("")) { // Two dates
			if (is_height_extend) {
				_height += 7;
			}
			_taskStartDateLabel.setBounds(
					_width - _taskStartDateLabel.getWidth() - 20
							- DSTASKTABLECELL_SHADOW_EXTEND_X, _height / 2
							- _taskStartDateLabel.getHeight() - 2, 60, 20);
			_taskEndDateLabel.setBounds(_width - _taskEndDateLabel.getWidth()
					- 20 - DSTASKTABLECELL_SHADOW_EXTEND_X, _height / 2 + 2,
					60, 20);
			this.add(_taskStartDateLabel);
			this.add(_taskEndDateLabel);
		} else if (!_task.StartDateandTime().equals("")) { // Only start date
			_taskStartDateLabel.setBounds(
					_width - _taskStartDateLabel.getWidth() - 20
							- DSTASKTABLECELL_SHADOW_EXTEND_X, _height / 2
							- _taskStartDateLabel.getHeight() / 2, 60, 20);
			this.add(_taskStartDateLabel);
		} else if (!_task.EndDateandTime().equals("")) { // Only end date
			_taskEndDateLabel.setBounds(_width - _taskEndDateLabel.getWidth()
					- 20 - DSTASKTABLECELL_SHADOW_EXTEND_X, _height / 2
					- _taskEndDateLabel.getHeight() / 2, 60, 20);
			this.add(_taskEndDateLabel);
		}

		// Reset layout size
		setLayoutSize(_width, _height);
		setVisible(true);
	}

	/**
	 * This function is used to set the size of cell
	 * 
	 * @param width
	 *            the width of cell
	 * @param height
	 *            the height of cell
	 */
	private void setLayoutSize(int width, int height) {
		setPreferredSize(new Dimension(width, height));
		setMinimumSize(new Dimension(width, height));
		setMaximumSize(new Dimension(width, height));
	}

	/**
	 * This function is used to process the graphics of cell
	 */
	private void processGraphics() {
		_graphicImage = new BufferedImage(_width, _height,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = _graphicImage.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		switch (_status) {
		case DSTask.DONE:
			drawDonePattern(g2d);
			break;
		case DSTask.OVERDUE:
			drawOverduePattern(g2d);
			break;
		case DSTask.IN_PROCESS:
			drawInProcessPattern(g2d);
			break;
		case DSTask.NOT_DONE:
			drawNotDonePattern(g2d);
			break;
		}

		drawTaskTypePattern(g2d);
		g2d.dispose();
	}

	/**
	 * This function is used to draw the done pattern
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawDonePattern(Graphics2D g2d) {
		drawDoneImage(g2d);
	}

	/**
	 * This function is used to draw overdue pattern
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawOverduePattern(Graphics2D g2d) {
		drawNotDoneBackground(g2d);
		drawOverdueImage(g2d);
	}

	/**
	 * This function is used to draw in process pattern
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawInProcessPattern(Graphics2D g2d) {
		drawNotDoneBackground(g2d);
		drawInProcessImage(g2d);
	}

	/**
	 * This function is used to draw not done pattern
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawNotDonePattern(Graphics2D g2d) {
		drawNotDoneBackground(g2d);
		drawNotDoneImage(g2d);
	}

	/**
	 * This function is used to draw task type pattern
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawTaskTypePattern(Graphics2D g2d) {
		if (_status != DSTask.DONE) {
			int x = _width - 10 - DSTASKTABLECELL_SHADOW_EXTEND_X;
			int y = DSTASKTABLECELL_SHADOW_EXTEND_Y;
			int width = 10;
			int height = _height - 2 * DSTASKTABLECELL_SHADOW_EXTEND_Y;

			if (_status == DSTask.OVERDUE) {
				drawFilledRoundRect(g2d, x, y, width, height, 0, 0,
						OVERDUE_COLOR);
			} else if (_status == DSTask.IN_PROCESS) {
				drawFilledRoundRect(g2d, x, y, width, height, 0, 0,
						INPROCESS_COLOR);
			}
		}
	}

	/**
	 * This function is used to draw done image
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawDoneImage(Graphics2D g2d) {
		int imageRadius = 10;
		int roundRadius = 20;

		int imageY = (_height - imageRadius) / 2;
		int roundY = (_height - roundRadius) / 2;

		int imageX = 10 + DSTASKTABLECELL_SHADOW_EXTEND_X;
		int roundX = roundRadius / 2 - 5 + DSTASKTABLECELL_SHADOW_EXTEND_X;

		drawFilledRoundRect(g2d, roundX, roundY, roundRadius, roundRadius, 60,
				60, DONE_COLOR);
		g2d.drawImage(_doneImage, imageX, imageY, imageRadius, imageRadius,
				null);
	}

	/**
	 * This function is used to draw overdue image
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawOverdueImage(Graphics2D g2d) {
		Font font = new Font("Arial", Font.BOLD, 16);
		int imageWidth = getFontMetrics(font).stringWidth("!");
		int imageHeight = getFontMetrics(font).getHeight();
		int roundRadius = 20;
		int imageY = (_height) / 2 + imageHeight / 4 + 2;
		int roundY = (_height - roundRadius) / 2;
		int imageX = (DSUILayout.TABLE_CELL_SIGN_WIDTH - imageWidth) / 2
				+ DSTASKTABLECELL_SHADOW_EXTEND_X - 5;
		int roundX = (DSUILayout.TABLE_CELL_SIGN_WIDTH - roundRadius) / 2
				+ DSTASKTABLECELL_SHADOW_EXTEND_X - 5;

		drawFilledRoundRect(g2d, roundX, roundY, roundRadius, roundRadius, 60,
				60, OVERDUE_COLOR);
		drawString(g2d, "!", font, imageX, imageY);
	}

	/**
	 * This function is used to draw in process image
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawInProcessImage(Graphics2D g2d) {
		int roundRadius = 16;
		int roundY = (_height - roundRadius) / 2;
		int roundX = (DSUILayout.TABLE_CELL_SIGN_WIDTH - roundRadius) / 2
				+ DSTASKTABLECELL_SHADOW_EXTEND_X - 5;
		drawFilledRoundRect(g2d, roundX, roundY, roundRadius, roundRadius, 20,
				20, INPROCESS_COLOR);
	}

	/**
	 * This function is used to draw not done image
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawNotDoneImage(Graphics2D g2d) {
		int checkRadius = 18;
		int checkY = (_height - checkRadius) / 2;
		int checkX = 10 + DSTASKTABLECELL_SHADOW_EXTEND_X - 5;
		drawdRoundRect(g2d, checkX, checkY, checkRadius, checkRadius, 100, 100,
				NOT_DONE_COLOR);
	}

	/**
	 * This function is used to draw not done background
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawNotDoneBackground(Graphics2D g2d) {
		int x = DSTASKTABLECELL_SHADOW_EXTEND_X;
		int y = DSTASKTABLECELL_SHADOW_EXTEND_Y;
		int width = 40 - DSTASKTABLECELL_SHADOW_EXTEND_X;
		int height = _height - 2 * DSTASKTABLECELL_SHADOW_EXTEND_Y;
		drawFilledRoundRect(g2d, x, y, width, height, 0, 0, new Color(67, 72,
				90, 55));
	}

	/**
	 * This function is used to draw string
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	private void drawString(Graphics2D g2d, String str, Font font, int x, int y) {
		g2d.setColor(Color.white);
		g2d.setFont(font);
		g2d.drawString(str, x, y);
	}

	/**
	 * This function is used to draw round rectangle
	 * 
	 * @param g2d
	 *            the graphics for painting
	 * @param x
	 *            the x value
	 * @param y
	 *            the y value
	 * @param width
	 *            the width
	 * @param height
	 *            the height
	 * @param arcWidth
	 *            the width of round corner
	 * @param arcHeight
	 *            the height of round corner
	 * @param color
	 *            color of round rectangle
	 */
	private void drawdRoundRect(Graphics2D g2d, int x, int y, int width,
			int height, int arcWidth, int arcHeight, Color color) {
		g2d.setStroke(new BasicStroke(2f));
		g2d.setColor(color);
		g2d.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
	}

	/**
	 * This function is used to draw filled round rectangle
	 * 
	 * @param g2d
	 *            the graphics for painting
	 * @param x
	 *            the x value
	 * @param y
	 *            the y value
	 * @param width
	 *            the width
	 * @param height
	 *            the height
	 * @param arcWidth
	 *            the width of round corner
	 * @param arcHeight
	 *            the height of round corner
	 * @param color
	 *            color of filled round rectangle
	 */
	private void drawFilledRoundRect(Graphics2D g2d, int x, int y, int width,
			int height, int arcWidth, int arcHeight, Color color) {
		g2d.setColor(color);
		g2d.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
	}

	/**
	 * This function is used to paint components
	 * 
	 * @param g2d
	 *            the graphics for painting
	 */
	@Override
	protected void paintComponent(Graphics g) {
		int x = DSTASKTABLECELL_SHADOW_EXTEND_X - 2;
		int y = DSTASKTABLECELL_SHADOW_EXTEND_Y - 2;
		int w = getWidth() - x * 2;
		int h = getHeight() - y * 2;
		int arc = DSTASKTABLECELL_ROUNDED_CORNER_RADIUS;

		// Background
		Graphics2D g2 = (Graphics2D) g.create();
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setColor(_backgroundColor);
		g2.fillRoundRect(x, y, w, h, arc, arc);

		if (_graphicImage != null)
			g.drawImage(_graphicImage, 0, 0, null);

		// Border
		g2.setStroke(new BasicStroke(2.5f));
		g2.setColor(_backgroundColor);
		g2.drawRoundRect(x + 1, y + 1, w - 2, h - 2, arc, arc);
		g2.dispose();
	}
}
